<?php

/**
 * Base SGN application class.
 * Sets up the environment as necessary and acts as the registry for the application.
 * Can broker to the autoload as well.
 *
 * @package SGN_Core
 * @version $Id: Application.php 1597 2012-04-03 03:53:18Z locth $
 * @author $Author: locth $
 *
 */
class SGN_Application extends Zend_Registry
{
    /**
     *
     * Singleton database instances
     *
     * @var array $_dbInstances
     *
     */
    private static $_dbInstances;
    /**
     * 
     * Static file version from config
     * 
     * @var integer
     */
    private static $_staticFileVersion;
    /**
     *
     * Singleton cache instances
     *
     * @var array $_dbInstances
     *
     */
    private static $_cacheInstances;
    /**
     *
     * Configuration instances
     *
     * @var array
     */
    private static $_configs;
    /**
     *
     * Static value for $_GET, $_POST
     * cleaned by ZF
     *
     * @var array
     */
    public static $params;
    
    public static $isLogedIn = false;

    /**
     *
     * Get database instances that's registered here
     *
     * @return array
     *
     */
    public function getDbInstances()
    {
        return self::$_dbInstances;
    }
    
    /**
     *
     * Get database instance
     * Guarantee singelton
     * 
     * Optional param $file to specify the file other than ENV.databases.ini
     *
     * @param string $product
     * @param string $dbInstance
     * @param string $file
     *
     * @return Zend_Db object
     */
    public static function getDbInstance($product, $dbInstance, $file = 'databases', $control = 'file')
    {
        if (is_object(self::$_dbInstances[$product][$dbInstance])) {
            return self::$_dbInstances[$product][$dbInstance];
        }
        if (! $product) {
            return false;
        }
        $config = self::getConfig($product, $file, $dbInstance, $control = 'file');
        if ($config instanceof Zend_Config) {
            self::$_dbInstances[$product][$dbInstance] = Zend_Db::factory($config);
            if (self::isDebugging()) {
                self::$_dbInstances[$product][$dbInstance]->getProfiler()->setEnabled(true);
            }
            return self::$_dbInstances[$product][$dbInstance];
        }
    }

    /**
     *
     * Get cache instance
     * Guarantee singelton
     *
     *
     * @param string $product
     * @param string $cacheInstance
     *
     * @return SGN_Cache
     * @deprecated
     */
    public static function getCacheInstance($product, $cacheInstance)
    {
        if (is_object(self::$_cacheInstances[$product][$cacheInstance])) {
            return self::$_cacheInstances[$product][$cacheInstance];
        }
        if (! $product) {
            return false;
        }
        $cacheConfig = self::getConfig($product, 'cache', $cacheInstance);
        if (! $cacheConfig instanceof Zend_Config) {
            throw new Exception("Cache configuration of \"$cacheInstance\" in \"$product\" does not exist");
        }
        if (SGN_Application::$params['nocache'] || isset($_GET['nocache'])) {
            $cacheConfig->__set('cache_enabled', false);
        }
        if ($cacheConfig->get('cache_enabled')) {
            $frontendOptions = array(
                                    'lifetime' => $cacheConfig->get('cache_lifetime'), 
                                    'write_control' => $cacheConfig->get('cache_writecontrol'), 
                                    'ignore_user_abort' => $cacheConfig->get('ignore_user_abort'), 
                                    'automatic_serialization' => $cacheConfig->get('cache_automatic_serialization'), 
                                    'caching' => $cacheConfig->get('cache_enabled'), 
                                    'cache_id_prefix' => $cacheConfig->get('cache_id_prefix')
            );
            $backendType = $cacheConfig->cache_backend->type;
            $option = $cacheConfig->cache_backend->options->Memcached->toArray();
            $config['frontend'] = 'Core';
            $config['backend'] = $backendType;
            $config['frontendOptions'] = $frontendOptions;
            $config['backendOptions'] = $option;
            $backendOptions = isset($config['backendOptions']) ? $config['backendOptions'] : array();
            $frontend = $config['frontend'];
            $backend = $config['backend'];
            $frontendOptions = isset($config['frontendOptions']) ? $config['frontendOptions'] : array();
            $customNameFrontend = isset($config['customNameFrondend']) ? $config['customNameFrondend'] : false;
            $customNameBackend = isset($config['customNameBackend']) ? $config['customNameBackend'] : false;
            $instance = SGN_Cache::factory($frontend, $backend, $frontendOptions, $backendOptions, $customNameFrontend, $customNameBackend);
            $cacheConfig = $cacheConfig->toArray();
            $cacheConfig['product'] = $product;
            $cacheConfig['instance'] = $cacheInstance;
            $instance->setOptions($cacheConfig);
            self::$_cacheInstances[$product][$cacheInstance] = $instance;
            return self::$_cacheInstances[$product][$cacheInstance];
        } else {
            return new SGN_Cache(null);
        }
    }

    /**
     *
     * Load cache instance using Zing Framework instance configuration and libraries
     * Guarantee singelton
     * 
     * Optional param $file to specify the file other than cache.ini
     *
     * NOTE: specifing return type help IDE regconizes code suggestion & auto completion
     *
     * @param string $product
     * @param string $instance
     * @param string $file
     *
     * @return Zing_Cache_Memcache 
     */
    public static function loadCacheInstance($product, $instance, $file = 'cache')
    {
        $config = self::getConfig($product, $file);
        if (! $config instanceof Zend_Config) {
            throw new Exception("Cache configuration of \"$instance\" in \"$product\" does not exist");
        }
        $cache = $instance;
        if (! isset($config->cachefarm->$cache)) {
            $cache = 'default';
            $instance = $cache;
        }
    
        if (SGN_Application::$params['nocache'] || isset($_GET['nocache'])) {
            $config->cachefarm->$cache->__set('enable', false);
        }
        Zing_Cache_ZingGlobalCache::$properties = "$product,$instance,$file";
        if (array_key_exists($instance, Zing_Cache_ZingGlobalCache::$_cache_array)) {
            return Zing_Cache_ZingGlobalCache::$_cache_array[$instance];
        }
        if (isset($config->cachefarm->$cache)) {
            $con = $config->cachefarm->$cache;
            if (! isset($con->type))
                $type = 'zingcache';
            else {
                $type = $con->type;
            }
            Zing_Cache_ZingGlobalCache::getCacheAdapter($cache, $type, $con);
        } else {
            $memcache = new Zing_Cache_NoCache();
            Zing_Cache_ZingGlobalCache::$_cache_array[$cache] = $memcache;
        }
        return Zing_Cache_ZingGlobalCache::$_cache_array[$cache];
    }

    /**
     *
     * Get url in site.ini configuration
     * Guarantee singelton
     *
     * @deprecated
     * @param string $namespace : url|image
     * @param string $configKey
     *
     * @return mix configValue, false if not configure
     */
    public static function getSiteConfig($namespace, $configKey = '')
    {
        $key = self::get($namespace);
        if ($key && isset($key[$configKey])) {
            return $key[$configKey];
        }
        return false;
    }

    /**
     * Get param from $_GET & $_POST
     * get param with default data.
     * @param string $key
     * @return string value, $default if not exist
     */
    public static function getParam($key, $default = '')
    {
        if (isset(self::$params[$key])) {
            return self::$params[$key];
        }
        return $default;
    }

    /**
     *
     * Get configuration
     * Guarantee singelton
     *
     *
     * @param string $module
     * @param string $name
     * @param string $configKey
     *
     * @return Zend_Config object
     */
    public static function getConfig($module, $name, $configKey = '', $control = 'file')
    {
        /**
         * Get config from database
         */
        if($control == 'db') {
            $config = SGN_SiteConfig::getConfig($module, $name, $configKey);
            if (!empty($config)) {
                return $config;
            }
        }
        if (self::$_configs[$module][$name] instanceof Zend_Config) {
            if ($configKey) {
                return self::$_configs[$module][$name]->get($configKey);
            }
            return self::$_configs[$module][$name];
        }
        
        if(IS_WORKER == 1) {//For worker
            $configFilePath = APPLICATION_PATH_WORKER . "/modules/$module/configs/" . APPLICATION_ENV . "/" . APPLICATION_ENV . ".$name.ini";
        } else {
            $configFilePath = APPLICATION_PATH . "/modules/$module/configs/" . APPLICATION_ENV . "/" . APPLICATION_ENV . ".$name.ini";
        }
        if (! Zend_Loader::isReadable($configFilePath)) {
            throw new Exception("Configuration file not found: \"$configFilePath\"");
        }
        if (! $module) {
            throw new Exception("Configuration namespace \"$module\" not found in \"$configFilePath\"");
        }
        if (! $name) {
            throw new Exception("Configuration name \"$name\" not found in namespace \"$module\" in \"$configFilePath\"");
        }
        $cache = GlobalCache::getCacheInstance('config');
        $configKeyName = APPLICATION_ENV . ".config.$module.$name"; // config.namespace.name
        
        $config = false;
        $cacheConfig = self::get('configuration');
        // disable staging config cache only
        if (strpos($_SERVER['SERVER_NAME'], 'staging')) {
            if ($cacheConfig->cachingfarm->config->enable_staging) {
                $config = $cache->getCache($configKeyName);
            }
        } else {
            $config = $cache->getCache($configKeyName);
        }
        if ($config === false) {
            if (SGN_Application::isRegistered('profiler')) {
                SGN_Application::get('profiler')->configCacheEnabled = false;
            }
            $config = new Zend_Config_Ini($configFilePath, APPLICATION_ENV, true);
            $cache->setCache($configKeyName, $config);
        }
        self::$_configs[$module][$name] = $config;
        if ($configKey) {
            return self::$_configs[$module][$name]->get($configKey);
        }
        return self::$_configs[$module][$name];
    }

    /**
     *
     * Set tooltip mode
     *
     * - set content type as javascript
     * - disable debug (or it will append text to javascript -> cannot display)
     *
     * @param mixed $response
     * @param boolean $ajaxMode is this AJAX request ?
     * @param string $contentType desire header
     * @param boolean $keepDebug
     * @param boolean $writeDebug
     *
     * @return void
     */
    public static function setTooltipMode($response, $ajaxMode = false, $contentType = 'text/javascript', $keepDebug = false, $writeDebug = false)
    {
        if (! $keepDebug) {
            SGN_Application::set('disableDebug', 1);
        }
        if ($writeDebug) {
            SGN_Application::set('writeDebug', 1);
        }
        if (! $ajaxMode) {
            $response->setHeader('Content-Type', $contentType);
        }
    }

    /**
     * Helper method to remove the result of magic_quotes_gpc being applied to the
     * input super globals
     *
     * @param array The array to have slashes stripped, this is passed by reference
     * @param integer Recursion depth to prevent malicious use
     *
     * @return void
     */
    public static function undoMagicQuotes(&$array, $depth = 0)
    {
        if ($depth > 10 || ! is_array($array)) {
            return;
        }
        foreach ($array as $key => $value) {
            if (is_array($value)) {
                self::undoMagicQuotes($array[$key], $depth + 1);
            } else {
                $array[$key] = stripslashes($value);
            }
            if (is_string($key)) {
                $new_key = stripslashes($key);
                if ($new_key != $key) {
                    $array[$new_key] = $array[$key];
                    unset($array[$key]);
                }
            }
        }
    }
    /**
     * 
     * Check login state
     * 
     * @return boolean
     * 
     */
    public static function isLoggedIn()
    {
    	$accName = SGN_SSO::validate(SGN_Application::getConfig('myrun', 'sso'));
        if ($accName) {
            return true;
        }
        return false;
    }
    /**
     * Check client ip match with allow client ip
     * Use in case: check whether is company ip to show error or debug.
     *
     * @param string $ip IP value to check
     * @param boolean $rangcheck
     *
     * @return boolean
     */
    public static function isInternalUser($ip = '', $rangecheck = false)
    {
        if (! SGN_Application::isRegistered('clientIp')) {
            return false;
        }
        $clientIp = SGN_Application::get('clientIp');
        if (isset($ip[0]) && ! $rangecheck) {
            return $clientIp === $ip;
        }
        $debugConfig = SGN_Application::getConfig('myrun', 'debug')->toArray();
        if (! $debugConfig) {
            return false;
        }
        if ($rangecheck) {
            $debugConfig['allowedIp'][] = $ip;
        }
        $excludeIp = implode('|', $debugConfig['excludeIp']);
        if (isset($excludeIp[0])) {
            if (preg_match("/{$excludeIp}/", $clientIp)) {
                return false;
            }
        }
        $allowClientIp = implode('|', $debugConfig['allowedIp']);
        $allowClientIp = str_replace(array(
                                            '.', 
                                            '*'
        ), array(
                '\.', 
                '.*'
        ), $allowClientIp);
        if (preg_match("/{$allowClientIp}/", $clientIp)) {
            return true;
        }
        return false;
    }

    /**
     *
     * Validate accounts that can be on debug modes and other stuff
     *
     * @return boolean
     *
     */
    public static function isAdminSite()
    {
        $debugConf = SGN_Application::getConfig('myrun', 'debug')->toArray();

        if (in_array(SGN_Session::get('username'), $debugConf['allowedAccounts'])) {
            return true;
        }
        return false;
    }

    /**
     *
     * Debug Mode Switcher
     *
     * @return boolean
     *
     */
    public static function isDebugging()
    {
        $debug = false;
        if (self::isInternalUser()) {
            $debug = true;
        }
        if (self::isAdminSite()) {
            $debug = true;
        }
        // MDT = Master Debug Turn (on/off)
        if (self::$params['MDT'] == 'on') {
            $debug = true;
        }
        if (self::$params['MDT'] == 'off') {
            $debug = false;
        }
        return $debug;
    }
    /**
     * Check to see if this request is AJAX
     * 
     * @return boolean
     */
    public static function isAJAX()
    {
        return (SGN_Application::get('isAjax') ? true : false);
    }
    /**
     *
     * Hash activty into 16,384 tables and 16 databases
     *
     *
     * @param integer $userId
     *
     * @return array
     */
    public static function hashActivity($userId)
    {
        $total = 0;
        $array = str_split($userId);
        foreach ($array as $char) {
            $total = 5 * $total % 16384 + $char;
        }
        $result['table'] = sprintf('%05d', $total % 16384);
        $result['db'] = sprintf('%02d', floor($result['table'] / 1024));
        // sanity check, make sure both tableName and DB is in range
        if (0 > intval($result['table']) || intval($result['table']) > 16383) {
            $result['table'] = - 1;
        }
        if (0 > intval($result['db']) || intval($result['db']) > 15) {
            $result['db'] = - 1;
        }
        return $result;
    }

    /**
     * encode output HTML or javascript string to protect XSS attack
     *
     * @param string $vars content need to clean up
     * @param boolean $addslass true for add slass to all ',",\
     * @param boolean $sageJavascript keep '<script></script>' in output string, by make a concat operator instead of  <script></script>
     * @param boolean $javascriptSringQuote quote(") or single quote (') which you use to make javascript string. 
     * 
     * @return string
     *
     */
    public static function safeHTMLOutput($vars, $addslass = false, $safeJavaScript = false, $javascriptSringQuote = "'")
    {
        if ($safeJavaScript) {
            $vars = addslashes($vars);
            $tmp = str_replace(' ', '', $vars);
            if (strpos($vars, '</script>') > 0) {
                $tmp = explode('script', $vars);
                $i = 0;
                foreach ($tmp as $k => $v) {
                    if ($i ++ % 2 == 0) {
                        $tmp[$k] = rtrim($v);
                    } else {
                        $tmp[$k] = ltrim($v);
                    }
                }
                $vars = implode("{$javascriptSringQuote} + {$javascriptSringQuote}script", $tmp);
            }
        } else {
            $vars = htmlentities($vars);
            if ($addslass) {
                $vars = addslashes($vars);
            }
        }
        return $vars;
    }
    public static function getStaticFileVersion()
    {
        $versionConfig = self::getConfig('default', 'global');
        
        return $versionConfig->staticVersion;
    }
    /**
     * Create XSToken for form validation
     *
     * @return string
     */
    public static function createXstoken() {
        $token = md5(uniqid(rand(), true));
        SGN_Application::get('session')->set('xstoken', $token);
        return $token;
    }
    /**
     * Format Unixtime to human readable time
     * If $normalDate = true, it simply format to Wednesday, February 29, 2012
     *
     * @param integer $date
     * @param boolean $normalDate
     *
     * @return string
     */
    public static function timeFormat($date, $normalDate = false) {
        return self::formatTime($date, $normalDate);
    } 
    /**
     * @deprecated please use timeFormat for better convention
     */
    public static function formatTime($date, $normalDate = false)
    {
        if ($normalDate) {
            $date = date("l, d M, Y h:ia", $date);
            $dayAndMonth = array(
                'Sunday' => 'ChuÌ‰ NhÃ¢Ì£t',
                'Monday' => 'ThÆ°Ì� Hai',
                'Tuesday' => 'ThÆ°Ì� Ba',
                'Wednesday' => 'ThÆ°Ì� TÆ°',
                'Thursday' => 'ThÆ°Ì� NÄƒm',
                'Friday' => 'ThÆ°Ì� SaÌ�u',
                'Saturday' => 'ThÆ°Ì� BaÌ‰y',
                'Jan' => 'ThaÌ�ng MÃ´Ì£t',
                'Feb' => 'ThaÌ�ng Hai',
                'Mar' => 'ThaÌ�ng Ba',
                'Apr' => 'ThaÌ�ng TÆ°',
                'May' => 'ThaÌ�ng NÄƒm',
                'Jun' => 'ThaÌ�ng SaÌ�u',
                'Jul' => 'ThaÌ�ng BaÌ‰y',
                'Aug' => 'ThaÌ�ng TaÌ�m',
                'Sep' => 'ThaÌ�ng ChiÌ�n',
                'Oct' => 'ThaÌ�ng MÆ°Æ¡Ì€i',
                'Nov' => 'ThaÌ�ng MÆ°Æ¡Ì€i MÃ´Ì£t',
                'Dec' => 'ThaÌ�ng MÆ°Æ¡Ì€i Hai',
            );
            return str_replace(array_keys($dayAndMonth), $dayAndMonth, $date);
        }
        if (empty($date)) {
            return 0;
        }
        $periods = array(
                        "giÃ¢y",
                        "phÃºt",
                        "giá»�",
                        "ngÃ y"
        );
        $lengths = array(
                        "60",
                        "60",
                        "24",
                        "7"
        );
        $now = time();
        $unix_date = $date;
        // check validity of date
        if (empty($unix_date)) {
            return "Bad date";
        }
        if ($now == $unix_date) {
             return "mÆ¡Ì�i Ä‘Ã¢y";
        } 
        // is it future date or past date
        if ($now > $unix_date) {
            $difference = $now - $unix_date;
            $tense = "trÆ°á»›c";
        } else {
            /*$difference = $unix_date - $now;
            $tense = "tá»« bÃ¢y giá»�";*/
        }
        for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j ++) {
            $difference /= $lengths[$j];
        }
        $difference = round($difference);
        if (date('Y') != date('Y', (float)$unix_date)) {
            $year = '-Y';
        }
        $datetime = date("H:i d-m$year", (float)$unix_date);
        if ($j == count($lengths) - 1) {
            if ($difference > 1)
                return $datetime;
            else
                return "hÃ´m qua";
        } else {
            return "$difference $periods[$j] {$tense}";
        }
    }
    /**
     * Get the current time of server.
     *
     * @return integer
     *
     */
    public static function getCurrentTime()
    {
        $time = 0;
        static $time;
        if ($time) {
            return $time;
        }
        return $time = time();
    }
    /**
     * Get the time of request.
     *
     * @return integer
     *
     */
    public static function getRequestTime()
    {
        $time = 0;
        static $time;
        if ($time) {
            return $time;
        }
        if (isset($_SERVER['REQUEST_TIME']) && $_SERVER['REQUEST_TIME']) {
            $time = intval($_SERVER['REQUEST_TIME']);
        } else {
            $time = time();
        }
        return $time;
    }
    /**
     * Get the logged in user.
     *
     * @return string
     *
     */
    public static function getCurrentUser()
    {
        $accountName = 0;
        static $accountName;
        Zend_Session::start();
        if (class_exists('SGN_Session')) {
            $accountName = SGN_Session::get('username');
            if (isset($accountName[0]) && SGN_Session::get('loggedin') == 1) {
                return $accountName;
            }
        }
        return '';
    }

    /**
     *
     * Convert bytes into KB, MB, GB, TB, or PB
     *
     * @param integer $a
     * @return string
     *
     */
    public static function bytesFormat($a)
    {
        $unim = array(
                    "B", 
                    "KB", 
                    "MB", 
                    "GB", 
                    "TB", 
                    "PB"
        );
        $c = 0;
        while ($a >= 1024) {
            $c ++;
            $a = $a / 1024;
        }
        return number_format($a, ($c ? 2 : 0), ",", ".") . " " . $unim[$c];
    }

    /**
     *
     * Convert bytes into KB, MB, GB, TB, or PB
     *
     * @param integer $number
     * @return string
     *
     */
    public static function numberFormat($number)
    {
        return number_format($number, 0, ',', '.');
    }

    private static function _extractIp($ip)
    {
        if (preg_match('/^([0-9]{1,3}\.){3,3}[0-9]{1,3}/', $ip, $array))
            return $array;
        else
            return false;
    }
    /**
     *
     * @insert a new array member at a given index
     *
     * @param array $array
     * @param mixed $new_element
     * @param int $index
     *
     * @return array
     *
     */
    public static function insertArrayIndex($array, $new_element, $index) {
         /*** get the start of the array ***/
         $start = array_slice($array, 0, $index); 
         /*** get the end of the array ***/
         $end = array_slice($array, $index);
         /*** add the new element to the array ***/
         $start[] = $new_element;
         /*** glue them back together and return ***/
         return array_merge($start, $end);
     }
    /**
     * Get remote IP
     * @return string
     */
    public static function getClientIp()
    {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']) { // case 1.A: proxy && HTTP_X_FORWARDED_FOR is defined
            $array = self::_extractIp($_SERVER['HTTP_X_FORWARDED_FOR']);
            if (isset($array[0])) {
                $clientIp = $array[0];
                return $clientIp; // first IP in the list
            }
        }
        if (isset($_SERVER['HTTP_X_FORWARDED']) && $_SERVER['HTTP_X_FORWARDED']) { // case 1.B: proxy && HTTP_X_FORWARDED is defined
            $array = self::_extractIp($_SERVER['HTTP_X_FORWARDED']);
            if (isset($array[0])) {
                $clientIp = $array[0];
                return $clientIp; // first IP in the list
            }
        }
        if (isset($_SERVER['HTTP_FORWARDED_FOR']) && $_SERVER['HTTP_FORWARDED_FOR']) { // case 1.C: proxy && HTTP_FORWARDED_FOR is defined
            $array = self::_extractIp($_SERVER['HTTP_FORWARDED_FOR']);
            if (isset($array[0])) {
                $clientIp = $array[0];
                return $clientIp; // first IP in the list
            }
        }
        if (isset($_SERVER['HTTP_FORWARDED']) && $_SERVER['HTTP_FORWARDED']) { // case 1.D: proxy && HTTP_FORWARDED is defined
            $array = self::_extractIp($_SERVER['HTTP_FORWARDED']);
            if (isset($array[0])) {
                $clientIp = $array[0];
                return $clientIp; // first IP in the list
            }
        }
        if (isset($_SERVER['HTTP_CLIENTIP']) && $_SERVER['HTTP_CLIENTIP']) { // case 1.E: proxy && HTTP_CLIENTIp is defined
            $array = self::_extractIp($_SERVER['HTTP_CLIENTIP']);
            if (isset($array[0])) {
                $clientIp = $array[0];
                return $clientIp; // first IP in the list
            }
        }
        if (isset($_SERVER['HTTP_VIA']) && $_SERVER['HTTP_VIA']) {
            // case 2:
            // proxy && HTTP_(X_) FORWARDED (_FOR) not defined && HTTP_VIA defined
            // other exotic variables may be defined
            $clientIp = ($_SERVER['HTTP_VIA'] . '_' . $_SERVER['HTTP_X_COMING_FROM'] . '_' . $_SERVER['HTTP_COMING_FROM']);
            return $clientIp; // first IP in the list
        }
        if ((isset($_SERVER['HTTP_X_COMING_FROM']) && $_SERVER['HTTP_X_COMING_FROM']) || (isset($_SERVER['HTTP_COMING_FROM']) && $_SERVER['HTTP_COMING_FROM'])) {
            // case 3: proxy && only exotic variables defined
            // the exotic variables are not enough, we add the REMOTE_ADDR of the proxy
            $clientIp = ($_SERVER['REMOTE_ADDR'] . '_' . $_SERVER['HTTP_X_COMING_FROM'] . '_' . $_SERVER['HTTP_COMING_FROM']);
            return $clientIp;
        }
        // case 4: no proxy (or tricky case: proxy+refresh)
        if (isset($_SERVER['REMOTE_HOST']) && $_SERVER['REMOTE_HOST']) {
            $array = self::_extractIp($_SERVER['REMOTE_HOST']);
            if (isset($array[0])) {
                $clientIp = $array[0];
                return $clientIp; // first IP in the list
            }
        }
        $clientIp = $_SERVER['REMOTE_ADDR'];
        return $clientIp;
    }

    /**
     * set cookie
     */
    public static function setCookie($key, $value, $expire = 0, $path = '', $domain = '')
    {
        $cookieConfig = self::getConfig('default', 'global', 'cookie');
        if (empty($expire) && $cookieConfig->expire) {
            if (!$value) {
                $expire = time() - intval($cookieConfig->expire);
            } else {
                $expire = time() + intval($cookieConfig->expire);
            }
        }
        if (!$path && $cookieConfig->path) {
            $path = $cookieConfig->path;
        }
        if (!$domain && $cookieConfig->domain) {
            $domain = $cookieConfig->domain;
        }
        setcookie($key, $value, $expire, $path, $domain);
        $_COOKIE[$key] = $value;
    }

    /**
     * get cookie
     * @return string
     */
    public static function getCookie($key)
    {
        return isset($_COOKIE[$key]) ? $_COOKIE[$key] : '';
    }
    // #############################################################################
    /**
    * Unicode-safe version of htmlspecialchars()
    *
    * @param	string	Text to be made html-safe
    *
    * @return	string
    */
    public static function htmlspecialchars_uni($text, $entities = true)
    {
    	if ($entities)
    	{
    		$text = preg_replace_callback(
    			'/&((#([0-9]+)|[a-z]+);)?/si',
    			'SGN_Application::htmlspecialchars_uni_callback',
    			$text
    		);
    	}
    	else
    	{
    		$text = preg_replace(
    			// translates all non-unicode entities
    			'/&(?!(#[0-9]+|[a-z]+);)/si',
    			'&amp;',
    			$text
    		);
    	}
    
    	return str_replace(
    		// replace special html characters
    		array('<', '>', '"'),
    		array('&lt;', '&gt;', '&quot;'),
    			$text
    	);
    }
    
    public static function htmlspecialchars_uni_callback($matches)
    {
     	if (count($matches) == 1)
     	{
     		return '&amp;';
     	}
    
    	if (strpos($matches[2], '#') === false)
    	{
    		// &gt; like
    		if ($matches[2] == 'shy')
    		{
    			return '&shy;';
    		}
    		else
    		{
    			return "&amp;$matches[2];";
    		}
    	}
    	else
    	{
    		// Only convert chars that are in ISO-8859-1
    		if (($matches[3] >= 32 AND $matches[3] <= 126)
    			OR
    			($matches[3] >= 160 AND $matches[3] <= 255))
    		{
    			return "&amp;#$matches[3];";
    		}
    		else
    		{
    			return "&#$matches[3];";
    		}
    	}
    }
    
    /**
     * Create global unique id
     * @param $namespace additional namspace if neccessary, default = ''
     * @param $md5format true if wanna return value is formatted in md5 style, default is false
     * */
    public static function create_uid($namespace = '', $md5format=false) {
    	static $guid = '';
    	$uid = uniqid("", true);
    	$data = $namespace;
    	$data .= $_SERVER['REQUEST_TIME'];
    	$data .= $_SERVER['HTTP_USER_AGENT'];
    	$data .= $_SERVER['LOCAL_ADDR'];
    	$data .= $_SERVER['LOCAL_PORT'];
    	$data .= $_SERVER['REMOTE_ADDR'];
    	$data .= $_SERVER['REMOTE_PORT'];
    	$hash = strtoupper(hash('ripemd128', $uid . $guid . md5($data)));
    	$guid = substr($hash,  0,  8) .
    	'-' .
    	substr($hash,  8,  4) .
    	'-' .
    	substr($hash, 12,  4) .
    	'-' .
    	substr($hash, 16,  4) .
    	'-' .
    	substr($hash, 20, 12);
    	if($md5format) {
    		$guid = md5($guid);
    	}
    
    	return $guid;
    }
    /**
     * Set main title
     * @param string $mainTitle
     * 
     * @return void
     */
    public static function setMainTitle($mainTitle = null){
        if($mainTitle == null) {
            $title = SGN_Application::getConfig(SGN_Application::$params['module'], 'maintitle', SGN_Application::$params['controller']);
            $action = SGN_Application::$params['action'];
            $title = $title->$action;
            $mainTitle = $title?$title:SGN_Application::getConfig(SGN_Application::$params['module'], 'maintitle', 'default');
        }
        SGN_Application::set('mainTitle',  $mainTitle);
    }
    
    /**
     * Push notification into server via Redis directly
     * @param array $channels
     * @param string $data data want to push
     * @param array $mailNotificationData mail data for send. It should be in format array('recipients' => array('receiver1@abc.com',
     * 																											'receiver2@abc.com'),
     * 																						'subject' => 'This is mail subject',
     * 																						'body' => 'Mail content',
                                                                                            'eventAttachment' => $attachment,
     * 																						'useHTML' => true,
     * 																						'encoding' => 'utf-8',
     *                                                                                       'shouldGroupWhenSending' => true,
     *                                                                                       'dateline' => 'unix time',
     *                                                                                       'type' => 'username/userid/email');
     * */
    public static function pushNotification($channels, $data, $mailNotificationData=null) {
    	$redisConfig = SGN_Application::getConfig('myrun', 'redis', 'notif');
    	$redis = SGN_Redis::getRedis($redisConfig->toArray());
    	if (!is_array($channels)) {
    		throw new Exception('Channels is not array');
    	}
//     	$message = array(
//     			'channels' => $channels,
//     			'data' => $data
//     	);
//     	//Push notification
//     	$result = $redis->publish('juggernaut', json_encode($message));
    	
    	$message = array(
    			'channels' => $channels,
    			'msg' => $data
    	);
    	//Push notification
    	$result = $redis->publish('kraken_channel_message', json_encode($message));
    	//Send Mail
    	if($mailNotificationData) {
    		self::sendMailToWorker($mailNotificationData['recipients'], 
    						$mailNotificationData['subject'], 
    						$mailNotificationData['body'],
    						$mailNotificationData['eventAttachment'],
    						$mailNotificationData['userHTML'],
    						$mailNotificationData['encoding'],
    		                $mailNotificationData['shouldGroupWhenSending'],
    		                $mailNotificationData['dateline'],
    		                $mailNotificationData['type']);
    	}
    	return $result;
    }
    
    /**
     * Send asynchronous mail to worker
     * @param string|array $recipients receivers
     * @param string $subject mail subject
     * @param string $body mail body
     * @param SGN_Communication_Event $eventAttachment event attachment. Default is null
     * @param boolean $useHTML indicate use html or not. Default 'false' as do not use HTML
     * @param string $charset 'utf-8' is default
     * @param boolean $shouldGroupWhenSending if true, this email will be grouped with other 
     * 			mails before send when they have the same receiver. Default is false
     * @param dateline timestamp in float
     * @param type 'email/username/userid'
     * @return boolean
     * @throws Exception
     * */
    public static function sendMailToWorker($recipients, $subject, $body, $eventAttachment=null, $useHTML=true, $charset='utf-8', $shouldGroupWhenSending=true, $dateline=false, $type='email') {
        if($type == 'email') {
            $data = self::processEmailData($recipients, $subject, $body, $eventAttachment, $useHTML, $charset, $shouldGroupWhenSending, $dateline, 'email');
        } else if($type == 'username') {
            $data = self::processEmailData($recipients, $subject, $body, $eventAttachment, $useHTML, $charset, $shouldGroupWhenSending, $dateline, 'username');
        } else if($type == 'userid') {
            $data = self::processEmailData($recipients, $subject, $body, $eventAttachment, $useHTML, $charset, $shouldGroupWhenSending, $dateline, 'userid');
        }
        if(isset($data)) {
        	$socketConfig = SGN_Application::getConfig('myrun' , 'handlersocket', 'notification_email_insert');
        	$handlerSocket = SGN_HandlerSocket::getSocket($socketConfig);
        	return $handlerSocket->executeInsert($socketConfig->openIndex, $data);
        }
    }
    
    /**
     * Send asynchronous mail to worker using username
     * @param string|array $usernames list of username
     * @param string $subject mail subject
     * @param string $body mail body
     * @param SGN_Communication_Event $eventAttachment event attachment. Default is null
     * @param boolean $useHTML indicate use html or not. Default 'false' as do not use HTML
     * @param string $charset iso-8859-1/utf-8
     * @param boolean $shouldGroupWhenSending if true, this email will be grouped with other 
     * 			mails before send when they have the same receiver. Default is false
     * @param dateline timestamp in float
     * @return boolean
     * @deprecated using sendEmailToWorker instead
     * @throws Exception
     * */
    public static function sendMailToWorkerUsingUserName($usernames, $subject, $body, $eventAttachment=null, $useHTML=false, $charset='iso-8859-1', $shouldGroupWhenSending=true, $dateline=false) {
        $data = self::processEmailData($usernames, $subject, $body, $eventAttachment, $useHTML, $charset, $shouldGroupWhenSending, $dateline, 'username');
    	$socketConfig = SGN_Application::getConfig('myrun' , 'handlersocket', 'notification_email_insert');
    	$handlerSocket = SGN_HandlerSocket::getSocket($socketConfig);
    	return $handlerSocket->executeInsert($socketConfig->openIndex, $data);
    }
    
    /**
     * process data
     * @param $type 'email/username/userid'
     * @return array
     * */
    private static function processEmailData($usernames, $subject, $body, $eventAttachment, $useHTML, $charset, $shouldGroupWhenSending, $dateline, $type='email') {
        if(!$usernames) {
            throw new Exception("Receivers is omitted, this param can be string or array");
        }
        if(!$subject) {
            throw new Exception("Mail subject is omitted, this param must be a value of string");
        }
        if(is_array($body)){
            try{
                $view = new Zend_View();
                $view->setScriptPath(ROOT_PATH . $body['scriptPath']);
                if(isset($body['filterPath'])){
                    $view->setFilterPath($body['filterPath']);
                }
                if(isset($body['helperPath'])){
                    $view->setHelperPath($body['helperPath']);
                }
                 
                foreach ($body['data'] as $key => $value){
                    $view->$key = $value;
                }
                $body = $view->render($body['viewName']);
            } catch(Exception $ex){
                throw  new Exception('Can not generate mail content: ' . $ex->getMessage());
            }
        }
        if(!$body) {
            throw new Exception("Mail body is omitted, this param must be a value of string and can be in HTML format");
        }
        if($eventAttachment !==null && !($eventAttachment instanceof SGN_Communication_Event)) {
            throw new Exception("Attachment param must be an instance of SGN_Communication_Event");
        }
        
        //Insert body into template
        $view = new Zend_View();
        $view->htmlContent = $body;
        $view->setScriptPath(ROOT_PATH . '/application/modules/sgn/layouts/scripts');
        $bodyWithHeaderFooter = $view->render('mail-header-footer.phtml');
        //Send mail
        $data = array($subject,
                $bodyWithHeaderFooter,
                json_encode($usernames),
                $eventAttachment ? serialize($eventAttachment) : null,
                $useHTML,
                $charset,
                $dateline?$dateline : time(),
                $shouldGroupWhenSending,
                $type);
        return $data;
    }
}